package com.greatchn.authorization_server.filter;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.greatchn.authorization_server.handler.UserDetailsHandler;
import com.greatchn.authorization_server.util.ApiAssert;
import com.greatchn.authorization_server.util.RedisClient;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @TokenFilter: token filter
 * @author: ZBoHang
 * @time: 2023/2/8 13:25
 */
@Component
public class TokenFilter extends GenericFilterBean {

    @Resource
    private UserDetailsHandler userDetailsHandler;
    @Resource
    private RedisClient redisClient;
    @Resource
    private JdbcClientDetailsService jdbcClientDetailsService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
        // TODO: 2023/2/8
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;

        String authToken = httpServletRequest.getHeader("token");

        Authentication authentication;

        if (StrUtil.isNotBlank(authToken)) {
            // 解析用户
            String oauthUserInfoJsonStr = this.redisClient.get(authToken);
            ApiAssert.noneBlank(oauthUserInfoJsonStr, "token过期!");
            String userName = JSONUtil.parseObj(oauthUserInfoJsonStr).getStr("userName");
            // 查询用户信息
            UserDetails details = this.userDetailsHandler.loadUserByUsername(userName);
            // 查询指向系统
            String directedSystem = httpServletRequest.getParameter("directed_system");
            // 申请访问资源相关路径
            if (StrUtil.isNotBlank(directedSystem)) {
                ClientDetails clientDetails = jdbcClientDetailsService.loadClientByClientId(directedSystem);
                ApiAssert.notNull(clientDetails, "指向系统不存在!");

                authentication = new OAuth2Authentication(
                        new OAuth2Request(
                                MapUtil.of(Pair.of("userName", userName)),
                                clientDetails.getClientId(),
                                clientDetails.getAuthorities(),
                                clientDetails.isAutoApprove(String.join(StrUtil.EMPTY, clientDetails.getScope())),
                                clientDetails.getScope(),
                                clientDetails.getResourceIds(),
                                null, null, MapUtil.of(Pair.of("userName", userName))
                        ),
                        new UsernamePasswordAuthenticationToken(details, details.getPassword(), details.getAuthorities()));

            } else {
                // 普通路径认证
                authentication = new UsernamePasswordAuthenticationToken(details, details.getPassword(), details.getAuthorities());
            }
            // 存放认证信息 恢复session - 若没有则会提示登录
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        // 调用后续的Filter,未能复原“session”，SecurityContext中没有想过信息，后面的流程会检测出"需要登录"
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
